package cn.amaake.magicplugin.nacos.util.parse;

import cn.amaake.magicplugin.nacos.util.AbstractConfigParse;
import com.alibaba.nacos.api.config.ConfigType;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

public class DefaultPropertiesConfigParse extends AbstractConfigParse {
    private static final Logger logger = LoggerFactory.getLogger(DefaultPropertiesConfigParse.class);

    public DefaultPropertiesConfigParse() {
    }

    public Map<String, Object> parse(String configText) {
        DefaultPropertiesConfigParse.OriginTrackedPropertiesLoader loader = new DefaultPropertiesConfigParse.OriginTrackedPropertiesLoader(new ByteArrayResource(configText.getBytes(Charset.defaultCharset())));

        try {
            return (Map)(StringUtils.hasText(configText) ? loader.load() : new LinkedHashMap());
        } catch (IOException var4) {
            IOException e = var4;
            throw new ConfigParseException(e);
        }
    }

    public String processType() {
        return ConfigType.PROPERTIES.getType();
    }

    class OriginTrackedPropertiesLoader {
        private final Resource resource;

        OriginTrackedPropertiesLoader(Resource resource) {
            Assert.notNull(resource, "Resource must not be null");
            this.resource = resource;
        }

        public Map<String, Object> load() throws IOException {
            return this.load(true);
        }

        public Map<String, Object> load(boolean expandLists) throws IOException {
            OriginTrackedPropertiesLoader.CharacterReader reader = new OriginTrackedPropertiesLoader.CharacterReader(this.resource);

            try {
                Map<String, Object> result = new LinkedHashMap();
                StringBuilder buffer = new StringBuilder();

                while(reader.read()) {
                    String key = this.loadKey(buffer, reader).trim();
                    if (expandLists && key.endsWith("[]")) {
                        key = key.substring(0, key.length() - 2);
                        int index = 0;

                        while(true) {
                            OriginTrackedValue valuex = this.loadValue(buffer, reader, true);
                            this.put(result, key + "[" + index++ + "]", valuex);
                            if (!reader.isEndOfLine()) {
                                reader.read();
                            }

                            if (reader.isEndOfLine()) {
                                break;
                            }
                        }
                    } else {
                        OriginTrackedValue value = this.loadValue(buffer, reader, false);
                        this.put(result, key, value);
                    }
                }
                return result;
            } finally {
                reader.close();
            }
        }

        private void put(Map<String, Object> result, String key, OriginTrackedValue value) {
            if (!key.isEmpty()) {
                result.put(key, value.value);
            }

        }

        private String loadKey(StringBuilder buffer, OriginTrackedPropertiesLoader.CharacterReader reader) throws IOException {
            buffer.setLength(0);
            boolean previousWhitespace = false;

            while(!reader.isEndOfLine()) {
                if (reader.isPropertyDelimiter()) {
                    reader.read();
                    return buffer.toString();
                }

                if (!reader.isWhiteSpace() && previousWhitespace) {
                    return buffer.toString();
                }

                previousWhitespace = reader.isWhiteSpace();
                buffer.append(reader.getCharacter());
                reader.read();
            }

            return buffer.toString();
        }

        private OriginTrackedValue loadValue(StringBuilder buffer, OriginTrackedPropertiesLoader.CharacterReader reader, boolean splitLists) throws IOException {
            buffer.setLength(0);

            while(reader.isWhiteSpace() && !reader.isEndOfLine()) {
                reader.read();
            }

           Location location = reader.getLocation();

            while(!reader.isEndOfLine() && (!splitLists || !reader.isListDelimiter())) {
                buffer.append(reader.getCharacter());
                reader.read();
            }

            TextResourceOrigin origin = DefaultPropertiesConfigParse.this.new TextResourceOrigin(this.resource, location);
            return OriginTrackedValue.of(buffer.toString(), origin);
        }

        private class CharacterReader implements Closeable {
            private final String[] ESCAPES = new String[]{"trnf", "\t\r\n\f"};
            private final LineNumberReader reader;
            private int columnNumber = -1;
            private boolean escaped;
            private int character;

            CharacterReader(Resource resource) throws IOException {
                this.reader = new LineNumberReader(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8));
            }

            public void close() throws IOException {
                this.reader.close();
            }

            public boolean read() throws IOException {
                return this.read(false);
            }

            public boolean read(boolean wrappedLine) throws IOException {
                this.escaped = false;
                this.character = this.reader.read();
                ++this.columnNumber;
                if (this.columnNumber == 0) {
                    this.skipLeadingWhitespace();
                    if (!wrappedLine) {
                        this.skipComment();
                    }
                }

                if (this.character == 92) {
                    this.escaped = true;
                    this.readEscaped();
                } else if (this.character == 10) {
                    this.columnNumber = -1;
                }

                return !this.isEndOfFile();
            }

            private void skipLeadingWhitespace() throws IOException {
                while(this.isWhiteSpace()) {
                    this.character = this.reader.read();
                    ++this.columnNumber;
                }

            }

            private void skipComment() throws IOException {
                if (this.character == 35 || this.character == 33) {
                    while(true) {
                        if (this.character == 10 || this.character == -1) {
                            this.columnNumber = -1;
                            this.read();
                            break;
                        }

                        this.character = this.reader.read();
                    }
                }

            }

            private void readEscaped() throws IOException {
                this.character = this.reader.read();
                int escapeIndex = this.ESCAPES[0].indexOf(this.character);
                if (escapeIndex != -1) {
                    this.character = this.ESCAPES[1].charAt(escapeIndex);
                } else if (this.character == 10) {
                    this.columnNumber = -1;
                    this.read(true);
                } else if (this.character == 117) {
                    this.readUnicode();
                }

            }

            private void readUnicode() throws IOException {
                this.character = 0;

                for(int i = 0; i < 4; ++i) {
                    int digit = this.reader.read();
                    if (digit >= 48 && digit <= 57) {
                        this.character = (this.character << 4) + digit - 48;
                    } else if (digit >= 97 && digit <= 102) {
                        this.character = (this.character << 4) + digit - 97 + 10;
                    } else {
                        if (digit < 65 || digit > 70) {
                            throw new IllegalStateException("Malformed \\uxxxx encoding.");
                        }

                        this.character = (this.character << 4) + digit - 65 + 10;
                    }
                }

            }

            public boolean isWhiteSpace() {
                return !this.escaped && (this.character == 32 || this.character == 9 || this.character == 12);
            }

            public boolean isEndOfFile() {
                return this.character == -1;
            }

            public boolean isEndOfLine() {
                return this.character == -1 || !this.escaped && this.character == 10;
            }

            public boolean isListDelimiter() {
                return !this.escaped && this.character == 44;
            }

            public boolean isPropertyDelimiter() {
                return !this.escaped && (this.character == 61 || this.character == 58);
            }

            public char getCharacter() {
                return (char)this.character;
            }

            public Location getLocation() {
                return new Location(this.reader.getLineNumber(), this.columnNumber);
            }
        }
    }

    public class TextResourceOrigin extends Origin {
        private final Resource resource;
        private final Location location;

        public TextResourceOrigin(Resource resource, Location location) {
            this.resource = resource;
            this.location = location;
        }

        public Resource getResource() {
            return this.resource;
        }

        public Location getLocation() {
            return this.location;
        }

        public int hashCode() {
            int result = 1;
            result = 31 * result + ObjectUtils.nullSafeHashCode(this.resource);
            result = 31 * result + ObjectUtils.nullSafeHashCode(this.location);
            return result;
        }

        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            } else if (obj == null) {
                return false;
            } else if (!(obj instanceof TextResourceOrigin)) {
                return super.equals(obj);
            } else {
                TextResourceOrigin other = (TextResourceOrigin)obj;
                boolean result = true;
                result = result && ObjectUtils.nullSafeEquals(this.resource, other.resource);
                result = result && ObjectUtils.nullSafeEquals(this.location, other.location);
                return result;
            }
        }

        public String toString() {
            StringBuilder result = new StringBuilder();
            result.append(this.resource != null ? this.resource.getDescription() : "unknown resource [?]");
            if (this.location != null) {
                result.append(":").append(this.location);
            }

            return result.toString();
        }
    }

    static class Location {
        private final int line;
        private final int column;

        public Location(int line, int column) {
            this.line = line;
            this.column = column;
        }

        public int getLine() {
            return this.line;
        }

        public int getColumn() {
            return this.column;
        }

        public int hashCode() {
            return 31 * this.line + this.column;
        }

        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            } else if (obj != null && this.getClass() == obj.getClass()) {
                Location other = (Location)obj;
                boolean result = true;
                result = result && this.line == other.line;
                result = result && this.column == other.column;
                return result;
            } else {
                return false;
            }
        }

        public String toString() {
            return this.line + 1 + ":" + (this.column + 1);
        }
    }

    public static class OriginTrackedValue implements OriginProvider {
        private final Object value;
        private final Origin origin;

        private OriginTrackedValue(Object value, Origin origin) {
            this.value = value;
            this.origin = origin;
        }

        public static OriginTrackedValue of(Object value) {
            return of(value, (Origin)null);
        }

        public static OriginTrackedValue of(Object value, Origin origin) {
            if (value == null) {
                return null;
            } else {
                return (OriginTrackedValue)(value instanceof CharSequence ? new OriginTrackedCharSequence((CharSequence)value, origin) : new OriginTrackedValue(value, origin));
            }
        }

        public Object getValue() {
            return this.value;
        }

        public Origin getOrigin() {
            return this.origin;
        }

        public String toString() {
            return this.value != null ? this.value.toString() : null;
        }

        public int hashCode() {
            return ObjectUtils.nullSafeHashCode(this.value);
        }

        public boolean equals(Object obj) {
            return obj != null && obj.getClass() == this.getClass() ? ObjectUtils.nullSafeEquals(this.value, ((OriginTrackedValue)obj).value) : false;
        }

        private static class OriginTrackedCharSequence extends OriginTrackedValue implements CharSequence {
            OriginTrackedCharSequence(CharSequence value, Origin origin) {
                super(value, origin);
            }

            public int length() {
                return this.getValue().length();
            }

            public char charAt(int index) {
                return this.getValue().charAt(index);
            }

            public CharSequence subSequence(int start, int end) {
                return this.getValue().subSequence(start, end);
            }

            public CharSequence getValue() {
                return (CharSequence)super.getValue();
            }
        }
    }

    public abstract static class Origin {
        public Origin() {
        }

        static Origin from(Object source) {
            if (source instanceof Origin) {
                return (Origin)source;
            } else {
                Origin origin = null;
                if (source != null && source instanceof OriginProvider) {
                    origin = ((OriginProvider)source).getOrigin();
                }

                return origin == null && source != null && source instanceof Throwable ? from(((Throwable)source).getCause()) : origin;
            }
        }
    }

    public interface OriginProvider {
        Origin getOrigin();
    }
}